<?php
namespace Yurun\RegionData;

/**
 * 导出类接口
 */
interface IGDExportHandler
{
    /**
     * 开始
     *
     * @return void
     */
    public function begin();

    /**
     * 写入
     *
     * @param string $cityCode
     * @param string $adCode
     * @param string $parentAdCode
     * @param string $name
     * @param string $longitude
     * @param string $latitude
     * @param string $level
     * @return void
     */
    public function write(string $cityCode, string $adCode, string $parentAdCode, string $name, string $longitude, string $latitude, string $level);

    /**
     * 结束
     *
     * @return void
     */
    public function end();

}

/**
 * 导出类基类
 */
abstract class BaseGDExportHandler implements IGDExportHandler
{
    /**
     * 应用类
     *
     * @var App
     */
    private $app;

    public function __construct($app)
    {
        $this->app = $app;
    }

    /**
     * Get 应用类
     *
     * @return App
     */ 
    public function getApp()
    {
        return $this->app;
    }

}

/**
 * CSV 导出类
 */
class GDExportCSVHandler extends BaseGDExportHandler
{
    /**
     * 保存文件名
     *
     * @var string
     */
    private $file;

    /**
     * delimiter
     *
     * @var string
     */
    private $delimiter;

    /**
     * enclosure
     *
     * @var string
     */
    private $enclosure;

    /**
     * 文件句柄
     *
     * @var resource
     */
    private $fp;

    /**
     * 开始
     *
     * @return void
     */
    public function begin()
    {
        $config = $this->getApp()->getConfig()['csv'] ?? [];
        $this->file = $config['file'] ?: (__DIR__ . '/export-' . time() . '.csv');
        $this->delimiter = $config['delimiter'] ?: '"';
        $this->enclosure = $config['enclosure'] ?: ',';
        $this->fp = fopen($this->file, 'w+');
        if($config['title'] ?? true)
        {
            fputcsv($this->fp, ['city_code', 'ad_code', 'parent_ad_code', 'name', 'longitude', 'latitude', 'level'], $this->delimiter, $this->enclosure);
        }
    }

    /**
     * 写入
     *
     * @param string $cityCode
     * @param string $adCode
     * @param string $parentAdCode
     * @param string $name
     * @param string $longitude
     * @param string $latitude
     * @param string $level
     * @return void
     */
    public function write(string $cityCode, string $adCode, string $parentAdCode, string $name, string $longitude, string $latitude, string $level)
    {
        fputcsv($this->fp, func_get_args(), $this->delimiter, $this->enclosure);
    }

    /**
     * 结束
     *
     * @return void
     */
    public function end()
    {
        fclose($this->fp);
    }

}

/**
 * MySQL 导出类
 */
class GDExportMySQLHandler extends BaseGDExportHandler
{
    /**
     * 主机
     *
     * @var string
     */
    private $host;

    /**
     * 端口
     *
     * @var string
     */
    private $port;

    /**
     * 用户名
     *
     * @var string
     */
    private $username;

    /**
     * 密码
     *
     * @var string
     */
    private $password;

    /**
     * 数据库名
     *
     * @var string
     */
    private $dbname;

    /**
     * 数据库对象
     *
     * @var \PDO
     */
    private $db;

    /**
     * 插入的 Statement
     *
     * @var \PDOStatement
     */
    private $insertStatement;

    /**
     * 开始
     *
     * @return void
     */
    public function begin()
    {
        $config = $this->getApp()->getConfig()['mysql'] ?? [];
        $this->host = $config['host'] ?: '127.0.0.1';
        $this->port = $config['port'] ?: 3306;
        $this->username = $config['username'] ?: 'root';
        $this->password = $config['password'] ?: 'root';
        $this->dbname = $config['dbname'] ?: '';
        $this->db = new \PDO(sprintf('mysql:host=%s;port=%s;dbname=%s;charset=utf8', $this->host, $this->port, $this->dbname), $this->username, $this->password);
        $this->db->exec('DROP TABLE IF EXISTS `tb_region`');
        $this->db->exec(<<<SQL
CREATE TABLE `tb_region`  (
  `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `parent_ad_code` char(6) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '父级区域编码',
  `city_code` char(4) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '城市编码',
  `ad_code` char(6) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '区域编码；街道没有独有的adcode，均继承父类（区县）的adcode',
  `name` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '行政区名称',
  `longitude` decimal(10, 7) NOT NULL COMMENT '经度',
  `latitude` decimal(10, 7) NOT NULL COMMENT '纬度',
  `level` char(8) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '行政区划级别；country:国家；province:省份（直辖市会在province和city显示）；city:市（直辖市会在province和city显示）；district:区县；street:街道',
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `city_code`(`city_code`) USING BTREE,
  INDEX `ad_code`(`ad_code`) USING BTREE,
  INDEX `level`(`level`) USING BTREE,
  INDEX `parent_ad_code`(`parent_ad_code`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact; 
SQL
        );
        $this->insertStatement = $this->db->prepare(<<<SQL
insert into `tb_region`(`city_code`,`ad_code`,`parent_ad_code`,`name`,`longitude`,`latitude`,`level`) values(:cityCode,:adCode,:parentAdCode,:name,:longitude,:latitude,:level)
SQL
        );
        $this->db->beginTransaction();
    }

    /**
     * 写入
     *
     * @param string $cityCode
     * @param string $adCode
     * @param string $parentAdCode
     * @param string $name
     * @param string $longitude
     * @param string $latitude
     * @param string $level
     * @return void
     */
    public function write(string $cityCode, string $adCode, string $parentAdCode, string $name, string $longitude, string $latitude, string $level)
    {
        $this->insertStatement->execute([
            ':cityCode'     =>  $cityCode,
            ':adCode'       =>  $adCode,
            ':parentAdCode' =>  $parentAdCode,
            ':name'         =>  $name,
            ':longitude'    =>  $longitude,
            ':latitude'     =>  $latitude,
            ':level'        =>  $level,
        ]);
    }

    /**
     * 结束
     *
     * @return void
     */
    public function end()
    {
        $this->db->commit();
    }

}


/**
 * JSON 导出类
 */
class GDExportJsonHandler extends BaseGDExportHandler
{
    /**
     * 列表数据的 JSON 文件名
     *
     * @var string
     */
    private $listFileName;

    /**
     * 关联数据的 JSON 文件名
     *
     * @var string
     */
    private $assocFileName;

    /**
     * 子地区集合的字段名
     *
     * @var string
     */
    private $childrenField;

    /**
     * 列表数据
     *
     * @var array
     */
    private $listData;

    /**
     * 开始
     *
     * @return void
     */
    public function begin()
    {
        $config = $this->getApp()->getConfig()['json'] ?? [];
        $this->listFileName = $config['listFileName'] ?: '';
        $this->assocFileName = $config['assocFileName'] ?: '';
        $this->childrenField = $config['childrenField'] ?: 'children';
        $this->listData = [];
    }

    /**
     * 写入
     *
     * @param string $cityCode
     * @param string $adCode
     * @param string $parentAdCode
     * @param string $name
     * @param string $longitude
     * @param string $latitude
     * @param string $level
     * @return void
     */
    public function write(string $cityCode, string $adCode, string $parentAdCode, string $name, string $longitude, string $latitude, string $level)
    {
        $this->listData[] = compact('cityCode', 'adCode', 'parentAdCode', 'name', 'longitude', 'latitude', 'level');
    }

    /**
     * 结束
     *
     * @return void
     */
    public function end()
    {
        file_put_contents($this->listFileName, json_encode($this->listData));
        file_put_contents($this->assocFileName, json_encode($this->toTreeAssoc($this->listData, 'adCode', 'parentAdCode', $this->childrenField)));
    }

    
    /**
     * 列表转树形关联结构.
     *
     * @param array  $list
     * @param string $idField
     * @param string $parentField
     * @param string $childrenField
     *
     * @return array
     */
    private function toTreeAssoc(array $list, $idField = 'id', $parentField = 'parent_id', $childrenField = 'children')
    {
        // 查出所有记录
        $result = $tmpArr = [];
        // 处理成ID为键名的数组
        foreach ($list as $item)
        {
            $item[$childrenField] = [];
            $tmpArr[$item[$idField]] = $item;
        }
        foreach ($tmpArr as $item)
        {
            if (isset($tmpArr[$item[$parentField]]))
            {
                $tmpArr[$item[$parentField]][$childrenField][] = &$tmpArr[$item[$idField]];
            }
            else
            {
                $result[] = &$tmpArr[$item[$idField]];
            }
        }

        return $result;
    }
}

// 执行主体
class App
{
    /**
     * IGDExportHandler
     *
     * @var IGDExportHandler
     */
    private $handler;

    /**
     * 配置
     *
     * @var array
     */
    private $config;

    public function __construct($configFile = null)
    {
        $this->config = include $configFile ?? (__DIR__ . '/gaode-config.php');
    }

    /**
     * 运行
     *
     * @return void
     */
    public function run()
    {
        $url = sprintf('http://restapi.amap.com/v3/config/district?key=%s&keywords=中国&subdistrict=3&extensions=base', $this->config['app_key']);

        echo '正在请求高德 API 接口...', PHP_EOL;
        $time = microtime(true);
        $content = file_get_contents($url);
        echo '接口耗时：', microtime(true) - $time, 's', PHP_EOL;
        
        $data = json_decode($content, true);
        if(!$data)
        {
            echo '数据解析失败', PHP_EOL;
            exit(1);
        }
        
        if(1 != ($data['status'] ?? 0))
        {
            echo '数据解析失败: ', $data['info'] ?? '', PHP_EOL;
            exit(1);
        }
        
        if(!($china = ($data['districts'][0] ?? null)))
        {
            echo '未找到 中国 数据', PHP_EOL;
            exit(1);
        }
    
        $handlerClass = $this->config['export_class'];
        /** @var IGDExportHandler $handler */
        $this->handler = new $handlerClass($this);
        $this->handler->begin();
        try {
            $this->writeToHandler('', $china);
        } finally {
            $this->handler->end();
        }
    }

    /**
     * 写入处理器
     *
     * @param string $parentAdCode
     * @param array $data
     * @return void
     */
    public function writeToHandler(string $parentAdcode, array $data)
    {
        [$latitude, $longitude] = explode(',', $data['center']);
        $this->handler->write($data['citycode'] ?: '', $data['adcode'], $parentAdcode, $data['name'], $longitude, $latitude, $data['level']);
        foreach($data['districts'] as $item)
        {
            $this->writeToHandler($data['adcode'], $item);
        }
    }

    /**
     * Get 配置
     *
     * @return array
     */ 
    public function getConfig()
    {
        return $this->config;
    }

}

$app = new App;
$app->run();
